package edu.cicese.alz.reports;

import edu.cicese.alz.GPSUtilities;
import edu.cicese.alz.LocationCluster;
import edu.cicese.alz.MapData;
import edu.cicese.alz.MarkerCluster;
import edu.cicese.alz.Query;
import edu.cicese.alz.RouteUtils;
import edu.cicese.alz.TimeUtil;
import edu.cicese.alz.swing.SwingUtil;
import edu.cicese.alz.util.IncenseUtil;
import edu.cicese.alz.util.PropertyUtil;
import edu.cicese.alz.util.Utilities;
import org.openstreetmap.gui.jmapviewer.MapHome;
import org.openstreetmap.gui.jmapviewer.MapLocation;
import org.openstreetmap.gui.jmapviewer.MapMarker;
import org.openstreetmap.gui.jmapviewer.MapTrack;

import javax.swing.SwingUtilities;
import java.awt.Color;
import java.io.File;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.TreeSet;

/**
 * Created by: Eduardo Quintana Contreras
 * Date: 4/07/12
 * Time: 03:17 PM
 */
public class GPSReport extends Report {
	private boolean dailyMap;
	private boolean complete = false;

	private Color colorNoData, colorHome, colorOut;

	private int homeRadius;

	private int layerType;
	private boolean showingMarkers, showingLocations, showingTracks, showingLabels;
	private boolean showingGrid, showingZoomControl, showingMarkerControl;
	private boolean showingHome;

	private Color markerBackground, markerBorder;

	private int locationRadius, locationMinTime, locationMergeRadius;
	private boolean mergingLocations;
	private Color locationBackground, locationBorder;
	private int locationSize;
	private boolean adjustLocationSize;
	private boolean showingLocationInfo;
	private int locationInfoType;

	private Color trackColor;
	private int trackThickness;

	private boolean showingDay, showingHour, showingDistance, showingTime, showingSpeed;

	//TODO Check implementation of location radius and location size

	public GPSReport(TreeSet<Integer> userIDs,
	                 Date beginDate, Date endDate, Date dayBeginDate, Date dayEndDate,
	                 boolean showCharts, boolean saveCharts) {

		super(userIDs, beginDate, endDate, dayBeginDate, dayEndDate, showCharts, saveCharts);
	}

	public GPSReport(TreeSet<Integer> userIDs, Date dayBeginDate, Date dayEndDate,
	                 boolean showCharts, boolean saveCharts) {

		super(userIDs, dayBeginDate, dayEndDate, showCharts, saveCharts);

		this.complete = true;
	}

	// Constructor for the Combined Report
	public GPSReport(long tsBegin, long tsEnd, int beginHour, int beginMinute, int beginSecond, int endHour,
	                 int endMinute, int endSecond, int days, int periods) {

		super(tsBegin, tsEnd, beginHour, beginMinute, beginSecond, endHour, endMinute, endSecond, days, periods);
	}


	public void setBlockSettings(int homeRadius, Color colorNoData, Color colorHome, Color colorOut) {
		this.homeRadius = homeRadius;
		this.colorNoData = colorNoData;
		this.colorHome = colorHome;
		this.colorOut = colorOut;

//		periods = periods / 10;
	}


	public void setMapSettings(int homeRadius, int layerType, boolean showingHome,
	                           boolean showingMarkers, boolean showingLocations, boolean showingTracks, boolean showingLabels,
	                           boolean showingGrid, boolean showingZoomControl, boolean showingMarkerControl) {

		this.homeRadius = homeRadius;

		this.layerType = layerType;
		this.showingHome = showingHome;
		this.showingMarkers = showingMarkers;
		this.showingLocations = showingLocations;
		this.showingTracks = showingTracks;
		this.showingLabels = showingLabels;
		this.showingGrid = showingGrid;
		this.showingZoomControl = showingZoomControl;
		this.showingMarkerControl = showingMarkerControl;
	}

	public void setMarkerSettings(Color markerBackground, Color markerBorder, int markerOpacity,
	                              boolean showingDay, boolean showingHour, boolean showingDistance, boolean showingTime, boolean showingSpeed) {

		this.markerBackground = SwingUtil.getTransparentColor(markerBackground, markerOpacity);
		this.markerBorder = SwingUtil.getTransparentColor(markerBorder, markerOpacity);
		// labels
		this.showingDay = showingDay;
		this.showingHour = showingHour;
		this.showingDistance = showingDistance;
		this.showingTime = showingTime;
		this.showingSpeed = showingSpeed;
	}

	public void setLocationSettings(int locationRadius, int locationMinTime, boolean mergingLocations, int locationMergeRadius,
	                                Color locationBackground, Color locationBorder, int locationOpacity, int locationSize,
	                                boolean showingLocationInfo, int locationInfoType, boolean adjustLocationSize) {

		this.locationRadius = locationRadius;
		this.locationMinTime = locationMinTime;
		this.mergingLocations = mergingLocations;
		this.locationMergeRadius = locationMergeRadius;
		this.locationBackground = SwingUtil.getTransparentColor(locationBackground, locationOpacity);
		this.locationBorder = SwingUtil.getTransparentColor(locationBorder, locationOpacity);
		this.locationSize = locationSize;
		this.showingLocationInfo = showingLocationInfo;
		this.adjustLocationSize = adjustLocationSize;
		this.locationInfoType = locationInfoType;
	}

	public void setTrackSettings(Color trackColor, int trackThickness, int trackOpacity) {
		this.trackColor = SwingUtil.getTransparentColor(trackColor, trackOpacity);
		this.trackThickness = trackThickness;
	}

	public void setDailyMap(boolean dailyMap) {
		this.dailyMap = dailyMap;
	}


	// Block
	public void createCharts() {
		String outputDir = "";
		if (saveCharts) {
			outputDir = IncenseUtil.getReportChartsDir("gps");
		}

		for (int userID : userIDs) {
			//TODO show error if mapHome null
			plot(userID, getValues(userID, getHome(userID)), outputDir);
		}
	}

	public double[][] getValues(int userID, MapHome mapHome) {
		ResultSet resultSet = Query.executeQuery(Query.queryGPSData(userID, tsBegin, tsEnd));
		List<GPSMeasure> gpsMeasures = new ArrayList<>();
		try {
			while (resultSet.next()) {
				gpsMeasures.add(new GPSMeasure(resultSet.getDouble(1), resultSet.getDouble(2), resultSet.getLong(3)));
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}

		double[][] values = new double[3][periods * days];

		Calendar cal = TimeUtil.getDayTimestamp(tsBegin);
		long dayTimestamp = cal.getTimeInMillis();
		long tsAux = dayTimestamp + startTime;

		if (!gpsMeasures.isEmpty()) {
			for (int day = 0; day < days; day++) {
				for (int p = 0; p < periods; p += 10) {
					double value = getValue(gpsMeasures, mapHome, tsAux);
					for (int pAux = p; pAux < p + 10; pAux++) {
						values[0][(day * periods + pAux)] = dayTimestamp;
						values[1][(day * periods + pAux)] = pAux * PERIOD_LENGTH + startTime;
						values[2][(day * periods + pAux)] = value;

						tsAux += PERIOD_LENGTH;
					}
				}

				cal.add(Calendar.DAY_OF_MONTH, 1);
				dayTimestamp = cal.getTimeInMillis();
				tsAux = dayTimestamp + startTime;
			}
		} else {
			for (int day = 0; day < days; day++) {
				for (int p = 0; p < periods; p++) {
					values[0][(day * periods + p)] = dayTimestamp;
					values[1][(day * periods + p)] = p * PERIOD_LENGTH + startTime;
					values[2][(day * periods + p)] = 0.5;
				}
				cal.add(Calendar.DAY_OF_MONTH, 1);
				dayTimestamp = cal.getTimeInMillis();
			}
		}

		return values;
	}

	private double getValue(List<GPSMeasure> gpsMeasures, MapHome mapHome, long tsAux) {
		long tsA = tsAux;
		long tsB = tsAux + PERIOD_LENGTH;

		double value = 0.5;
		boolean found = false;

		while (!gpsMeasures.isEmpty()) {
			GPSMeasure gpsMeasure = gpsMeasures.get(0);
			long ts = gpsMeasure.getTimestamp();

			if (ts > tsB) {
				break;
			}

			if (ts > tsA && ts <= tsB) {
				if (!found) {
					value = 1.5;
					if (mapHome == null || !isHome(mapHome.getLat(), mapHome.getLon(), gpsMeasure.getLat(), gpsMeasure.getLon())) {
						value = 2.5;
						found = true;
					}
				}
			}

			gpsMeasures.remove(0);
		}

		return value;
	}

	private void plot(int userID, double[][] values, String outputDir) {

		FrameGPSBlock frameGPSBlock = new FrameGPSBlock(userID, startTime, colorNoData, colorHome, colorOut);

		if (saveCharts) {
			frameGPSBlock.setOutputDir(outputDir);
		}

		frameGPSBlock.plotData(values, days);

		if (showCharts) {
			frameGPSBlock.pack();
			frameGPSBlock.setVisible(true);
		} else {
			frameGPSBlock.dispose();
		}
	}


	// Maps
	public void createMaps() {
		String outputDir = "";
		if (saveCharts) {
			outputDir = PropertyUtil.getReportChartsDir() + File.separator + "gps" +
					File.separator + TimeUtil.getReportDate(System.currentTimeMillis()) + File.separator;
		}

		for (Integer userID : userIDs) {

			if (complete) {
				Date[] dates = ReportPane.getDates(userID, ReportPane.GPS);
				tsBegin = dates[0].getTime();
				tsEnd = dates[1].getTime();
			}

			MapHome mapHome = showingHome ? getHome(userID) : null;

			MapData mapData = new MapData(showingHome, showingMarkers, showingLocations, showingTracks, showingLabels,
					showingGrid, showingZoomControl, showingMarkerControl, showingLocationInfo, adjustLocationSize,
					locationInfoType, homeRadius, layerType);

			int days = TimeUtil.getDayDifference(tsBegin, tsEnd - 1);
			Calendar cal = TimeUtil.getDayTimestamp(tsBegin);
			long dayTimestamp = cal.getTimeInMillis();

			int counter = 0;
			List<MapLocation> mapLocations = new ArrayList<>();
//			mapData.setMapLocations(mergeLocations(mapData.getMapLocations()));

			for (int day = 0; day < days; day++) {
				long beginTimestamp = TimeUtil.getAdjustedTimestamp(dayTimestamp, beginHour, beginMinute, beginSecond);
				long endTimestamp = TimeUtil.getAdjustedTimestamp(dayTimestamp, endHour, endMinute, endSecond);

				getMapData(mapData, Query.executeQuery(Query.queryGPSData(userID, beginTimestamp, endTimestamp)));
				Query.close();

				if (dailyMap) {
					createMap(userID, mapData, mapHome, TimeUtil.getDay(dayTimestamp), outputDir);

					for (MapLocation l : mapData.getMapLocations()) {
						if (!l.isHome()) {
							counter++;
							if (!mapLocations.contains(l)) {
								mapLocations.add(l);
								mapLocations = mergeLocations(mapLocations);
							}
						}
					}
					System.out.println("\t" + mapLocations.size() + "\t" + counter);

					mapData = new MapData(showingHome, showingMarkers, showingLocations, showingTracks, showingLabels,
							showingGrid, showingZoomControl, showingMarkerControl, showingLocationInfo, adjustLocationSize,
							locationInfoType, homeRadius, layerType);
				}

				cal.add(Calendar.DAY_OF_MONTH, 1);
				dayTimestamp = cal.getTimeInMillis();
			}

			if (!dailyMap) {
				String date = tsBegin != tsEnd ? TimeUtil.getDay(tsBegin) + "_" + TimeUtil.getDay(tsEnd - 1) : TimeUtil.getDay(tsBegin);
				createMap(userID, mapData, mapHome, date, outputDir);
			}
		}
	}

	private void createMap(int userID, MapData mapData, MapHome mapHome, String date, String outputDir) {
		mapData.setMapLocations(getLocationsOfInterest(getMarkerClusters(mapData.getMapMarkers())));

		if (mergingLocations) {
			mapData.setMapLocations(mergeLocations(mapData.getMapLocations()));
		}
		if (mapHome != null) {
			removeHomeLocations(mapData.getMapLocations(), mapHome);
		}

		mapData.setUserID(userID);
		mapData.setMapHome(mapHome);

		if (showCharts) {
			FrameMap frameMap = new FrameMap(mapData, date);
			//TODO save without showing
			if (saveCharts) {
				saveMaps(frameMap, outputDir, getFilename(userID, date));
			}
		}
//		printReport(mapData, mapHome, date);
		printReport2(mapData, mapHome, date);
	}

	private void printReport(MapData mapData, MapHome mapHome, String date) {
		double distancePOIs = 0, distanceMarkers = 0;
		for (MapLocation l : mapData.getMapLocations()) {
			if (!l.isHome()) {
//				System.out.println(l.getMapLocationID() + " " + l.getIncidences() + " " + l.getTimeSpent() + " " + l.getLat() + " " + l.getLon() + " " +
//						l.getLat1() + " " + l.getLon1() + " " +
//						l.getLat2() + " " + l.getLon2() + " " +
//						l.getMinLat() + " " + l.getMinLon() + " " +
//						l.getMaxLat() + " " + l.getMaxLon());
				double d = GPSUtilities.getSLCDistanceFrom(mapHome.getLat(), mapHome.getLon(), l.getLat(), l.getLon());
				distancePOIs = d > distancePOIs ? d : distancePOIs;
			}
		}
		for (MapMarker m : mapData.getMapMarkers()) {
			double d = GPSUtilities.getSLCDistanceFrom(mapHome.getLat(), mapHome.getLon(), m.getLat(), m.getLon());
			distanceMarkers = d > distanceMarkers ? d : distanceMarkers;
		}
		System.out.println(date + " " + distancePOIs + " " + distanceMarkers);
	}

	private void printReport2(MapData mapData, MapHome mapHome, String date) {
		int counter = 0;
		double distancePOIs = 0;
		for (MapLocation l : mapData.getMapLocations()) {
			if (!l.isHome()) {
				counter++;
				double d = GPSUtilities.getSLCDistanceFrom(mapHome.getLat(), mapHome.getLon(), l.getLat(), l.getLon());
				distancePOIs = d > distancePOIs ? d : distancePOIs;
			}
		}
		System.out.print(date + "\t" + counter + "\t" + distancePOIs);
	}

	public void getMapData(MapData mapData, ResultSet resultSet) {
		double latitude, longitude, auxLatitude = 0, auxLongitude = 0;
//		double altitude;
//		int accuracy;
		long timestamp, auxTimestamp = 0;
		double speed;
		long time;
		double distance = 0;
		String label, speedUnit;

		List<MapMarker> mapMarkers = mapData.getMapMarkers();
		List<MapTrack> mapTracks = mapData.getMapTracks();
		int counter = mapMarkers.size();

		try {
			while (resultSet.next()) {
				latitude = resultSet.getDouble(1);
				longitude = resultSet.getDouble(2);
				timestamp = resultSet.getLong(3);
//				altitude = resultSet.getDouble(4);
//				accuracy = resultSet.getInt(5);

				label = "";

				if (counter >= 1) {
					distance = GPSUtilities.getSLCDistanceFrom(auxLatitude, auxLongitude, latitude, longitude);
					time = (timestamp - auxTimestamp) / 1000;
					speed = distance / (double) time;
					speedUnit = "m/s";

					if (showingDay) {
						label += TimeUtil.getStringDateFromMs(timestamp, TimeUtil.calendarDateFormat) + " - ";
					}
					if (showingHour) {
						label += TimeUtil.getStringDateFromMs(timestamp, TimeUtil.timeFormat) + " - ";
					}
					if (showingDistance) {
						label += String.format("%.1f", distance) + "m" + " - ";
					}
					if (showingTime) {
						label += time + "s" + " - ";
					}
					if (showingSpeed) {
						label += String.format("%.2f", speed) + speedUnit + " - ";
					}

					mapTracks.add(new MapTrack(auxLatitude, auxLongitude, latitude, longitude, trackColor, trackThickness - 1));
				}
				else {
					if (showingDay) {
						label += TimeUtil.getStringDateFromMs(timestamp, TimeUtil.calendarDateFormat) + " - ";
					}
					if (showingHour) {
						label += TimeUtil.getStringDateFromMs(timestamp, TimeUtil.timeFormat) + " - ";
					}
				}

				if (!label.equals("")) {
					label = label.substring(0, label.length() - 3);
				}

				mapMarkers.add(new MapMarker(latitude, longitude, timestamp, distance, markerBackground, markerBorder, label));

				auxLatitude = latitude;
				auxLongitude = longitude;
				auxTimestamp = timestamp;

				counter++;
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void saveMaps(final FrameMap frameMap, final String outputDir, final String filename) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				frameMap.saveMap(outputDir, filename);
				if (!showCharts) {
					frameMap.setVisible(false);
					frameMap.dispose();
				}
			}
		});
	}

	/**
	 * Gets a list of <code>MapMarker</code> groups.
	 *
	 * @param mapMarkers list of markers
	 * @return a list of grouped <code>MapMarkers</code>
	 */
	private List<MarkerCluster> getMarkerClusters(List<MapMarker> mapMarkers) {
		List<MarkerCluster> markerClusters = new ArrayList<>();
		int mapMarkersSize = mapMarkers.size();

		for (int i = 0; i < mapMarkersSize; i++) {
			MarkerCluster markerCluster = new MarkerCluster();
			MapMarker m1 = mapMarkers.get(i);
			markerCluster.add(m1);

			// Add previous marker
			if (i != 0) {
				markerCluster.setMarkerA(mapMarkers.get(i - 1));
			}

			// Add the next markers, as long as they are inside the radius specified
			int j;
			for (j = i + 1; j < mapMarkersSize; j++) {
				MapMarker m2 = mapMarkers.get(j);
				if (GPSUtilities.getSLCDistanceFrom(m1.getLat(), m1.getLon(), m2.getLat(), m2.getLon()) <= locationRadius) {
					markerCluster.add(m2);
				} else {
					break;
				}
			}

			// Add next marker
			if (j < mapMarkersSize) {
				markerCluster.setMarkerB(mapMarkers.get(j));
			}

			markerClusters.add(markerCluster);
		}

		// Sort by quantity of markers in the cluster
		Collections.sort(markerClusters);

		return markerClusters;
	}

	/**
	 * Gets a list of <code>MapLocation</code> elements.
	 *
	 * @param markerClusters list of <code>MarkerCluster</code> elements,
	 *                       which are groups of <code>MapMarker</code> elements
	 * @return a list of <code>MapLocation</code> elements
	 */
	private List<MapLocation> getLocationsOfInterest(List<MarkerCluster> markerClusters) {
		List<MapLocation> mapLocations = new ArrayList<>();
		List<MapMarker> currentAddedMarkers = new ArrayList<>();
		List<MapMarker> globalAddedMarkers = new ArrayList<>();
		long timeSpentInside;

		for (MarkerCluster cluster : markerClusters) {
			// Remove already added markers to another location of interest
			cluster.removeDuplicates(globalAddedMarkers);

			// Calculate user's time spent in the cluster
			if (!cluster.getMarkers().isEmpty()) {
				timeSpentInside = getTimeSpentInArea(cluster);

				// If the user spent more than X time inside the location
				if (timeSpentInside >= locationMinTime * 60) {
					currentAddedMarkers.addAll(cluster.getMarkers());
					globalAddedMarkers.addAll(cluster.getMarkers());

					double[] maxMin = getMaxMin(currentAddedMarkers);

					MapLocation mapLocation = new MapLocation(maxMin[0], maxMin[1], locationBackground, locationBorder,
							maxMin[2], maxMin[3], maxMin[4], maxMin[5], locationSize, locationRadius, 1,
							(int) (timeSpentInside / 60), locationInfoType, showingLocationInfo, adjustLocationSize);

//					if (showingLocationInfo) {
//						String label;
//						int quantity;
//						if (locationInfoType == MapLocation.TYPE_INCIDENCES) {
//							quantity = cluster.getQuantity();
//						}
//						else {
//							quantity = (int) (timeSpentInside / 60);
//						}
//						label = quantity + "";
//						if (!adjustLocationSize) {
//							mapLocation = new MapLocation(maxMin[0], maxMin[1], locationBackground, locationBorder,
//									maxMin[2], maxMin[3], maxMin[4], maxMin[5], locationRadius, label);
//						}
//						else {
//							mapLocation = new MapLocation(maxMin[0], maxMin[1], locationBackground, locationBorder,
//									maxMin[2], maxMin[3], maxMin[4], maxMin[5], label, quantity, locationInfoType);
//						}
//					}
//					else {
//						mapLocation = new MapLocation(maxMin[0], maxMin[1], locationBackground, locationBorder,
//								maxMin[2], maxMin[3], maxMin[4], maxMin[5], locationRadius);
//					}

					mapLocation.setMapLocationID(RouteUtils.getMapLocationCounter());
					mapLocation.addMapMarkers(currentAddedMarkers);
					mapLocations.add(mapLocation);

					currentAddedMarkers.clear();
				}
			}
		}

		return mapLocations;
	}

	/**
	 * Gets the time in seconds spent inside a <code>MakerCluster</code>
	 *
	 * @param markerCluster a <code>MarkerCluster</code> object
	 * @return the time in seconds in a <code>MarkerCluster</code>
	 */
	private long getTimeSpentInArea(MarkerCluster markerCluster) {
		long /*timeA, timeB, */time;
		List<MapMarker> mapMarkers = markerCluster.getMarkers();
		MapMarker m0 = mapMarkers.get(0); // first marker in the cluster
		MapMarker mn = mapMarkers.get(mapMarkers.size() - 1); // last marker in the cluster

		time = mn.getTimestamp() - m0.getTimestamp();

		return time / 1000;

//		if (markerCluster.getMarkerA() != null) {
//			timeA = m0.getTimestamp() - markerCluster.getMarkerA().getTimestamp();
//			timeA = (long) (timeA / m0.getDistanceFromPrevious()) * locationRadius;
//		}
//
//		if (markerCluster.getMarkerB() != null) {
//			timeB = markerCluster.getMarkerB().getTimestamp() - mn.getTimestamp();
//			timeB = (long) (timeB / markerCluster.getMarkerB().getDistanceFromPrevious()) * locationRadius;
//		}
//
//		return (time + timeA + timeB) / 1000;
	}

	/**
	 * Gets maximum and minimum latitude and longitude from a list of <code>MapMarker</code> elements.
	 *
	 * @param markers list of <code>MapMarker</code> elements
	 * @return an array of <code>size = 6</code>, containing:
	 *         center latitude, center longitude, minimum latitude,
	 *         minimum longitude, maximum latitude and maximum longitude
	 */
	private double[] getMaxMin(List<MapMarker> markers) {
		double minLat = 90, maxLat = -90;
		double minLon = 180, maxLon = -180;
		double lat, lon;

		for (MapMarker marker : markers) {
			lat = marker.getLat();
			lon = marker.getLon();
			if (lat < minLat) {
				minLat = lat;
			}
			if (lat > maxLat) {
				maxLat = lat;
			}
			if (lon < minLon) {
				minLon = lon;
			}
			if (lon > maxLon) {
				maxLon = lon;
			}
		}
		lat = minLat + (maxLat - minLat) / 2;
		lon = minLon + (maxLon - minLon) / 2;

		return new double[]{lat, lon, minLat, minLon, maxLat, maxLon};
	}

	/**
	 * Merges <code>MapLocation</code> elements within a given <code>locationMergeRadius</code>.
	 *
	 * @param mapLocations list of <code>MapLocation</code> elements
	 * @return a new list with the merged <code>MapLocation</code> elements
	 */
	private List<MapLocation> mergeLocations(List<MapLocation> mapLocations) {
		// Group locations
		List<LocationCluster> locationClusters = new ArrayList<>();
		for (MapLocation l1 : mapLocations) {
			LocationCluster locationCluster = new LocationCluster();
			locationCluster.add(l1);
			for (MapLocation l2 : mapLocations) {
				if (l1 != l2) {
					if (GPSUtilities.getSLCDistanceFrom(l1.getLat(), l1.getLon(), l2.getLat(), l2.getLon()) <= locationMergeRadius) {
						locationCluster.add(l2);
					}
				}
			}
			locationClusters.add(locationCluster);
		}
		Collections.sort(locationClusters);

		// Remove duplicated groups
		List<LocationCluster> finalLocationClusters = new ArrayList<>();
		for (LocationCluster lc1 : locationClusters) {
			if (!lc1.getLocations().isEmpty()) {
				LocationCluster newLocation = new LocationCluster();
				newLocation.getLocations().addAll(lc1.getLocations());
				for (LocationCluster lc2 : locationClusters) {
					if (lc1 != lc2) {
						if (lc2.getLocations().removeAll(newLocation.getLocations())) {
							newLocation.getLocations().addAll(lc2.getLocations());
						}
					}
				}
				finalLocationClusters.add(newLocation);
			}
		}

		// Obtain the average center of the location
		List<MapLocation> finalMapLocations = new ArrayList<>();
		for (LocationCluster locationCluster : finalLocationClusters) {
			double[] maxMin = locationCluster.getMaxMin();
			locationCluster.calculateTimeSpent();
			MapLocation mapLocation = new MapLocation(maxMin[0], maxMin[1], locationBackground, locationBorder,
					maxMin[2], maxMin[3], maxMin[4], maxMin[5], locationSize, locationRadius, locationCluster.getLocations().size(),
					locationCluster.getTimeSpent(), locationInfoType, showingLocationInfo, adjustLocationSize);

			finalMapLocations.add(mapLocation);
		}

		return finalMapLocations;
	}


	private String getFilename(int userID, String date) {
		return Utilities.addZeros(userID, 2) + " - " + date + ".png";
	}


	private MapHome getHome(int userID) {
		MapHome mapHome = null;

		ResultSet resultSet = Query.executeQuery(Query.querySubjectHome(userID));
		try {
			if (resultSet.next()) {
				double latitude = resultSet.getDouble(1);
				double longitude = resultSet.getDouble(2);
				if (latitude != 0 && longitude != 0) {
					mapHome = new MapHome(latitude, longitude, homeRadius);
				}
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			Query.close();
		}

		return mapHome;
	}

	private void removeHomeLocations(List<MapLocation> mapLocations, MapHome mapHome) {
		for (MapLocation location : mapLocations) {
			location.setHome(isHome(mapHome.getLat(), mapHome.getLon(), location.getLat(), location.getLon()));
		}
	}

	private boolean isHome(double lat1, double lon1, double lat2, double lon2) {
		return GPSUtilities.getSLCDistanceFrom(lat1, lon1, lat2, lon2) <= homeRadius;
	}
}
